using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Evolution.Engine.Managers;

namespace Evolution.Engine.SpatialTrees
{
    public class Scenenode
    {
        private Vector3 _Center;
        private float _Length;

        private ScenenodeCollection _Nodes;
        private ObjectCollection _Objects;

        public ScenenodeCollection Nodes { get { return _Nodes; } }
        public ObjectCollection Objects { get { return _Objects; } }

        public BaseObject Find(string Name)
        {
            for (int i = 0; i < _Objects.Count; i++)
            {
                if (_Objects[i].Name.ToLower() == Name.ToLower())
                    return _Objects[i];
            }

            // Draw the children down the tree
            for (int j = 0; j < _Nodes.Count; j++)
            {
                BaseObject tmp = _Nodes[j].Find(Name);
                if (tmp != null)
                    return tmp;
            }

            return null;
        }

        private VertexPositionColor[] points;
        private int[] index;

        private BoundingBox _Bounds;
        public bool Culled;

        public Scenenode(Vector3 newCenter, float newLength)
        {
            _Center = newCenter;
            _Length = newLength;

            _Nodes = new ScenenodeCollection();
            _Objects = new ObjectCollection();

            LoadGraphicsContent();
        }

        public void AddObject(BaseObject newObject)
        {
            if (_Nodes.Count != 0)
            {
                for (int j = 0; j < _Nodes.Count; j++)
                {
                    if (_Nodes[j].Contains(newObject.Position))
                    {
                        _Nodes[j].AddObject(newObject);
                    }
                }

            }
            else if (_Objects.Count >= Scene.MaxObjectsPerNode)
            {
                // If no children, see if we are already at the max capacity
                // and split and redistribute the objects if we are

                _Objects.Add(newObject);

                Split();
                DistributeObjects();

            }
            else
            {
                // Otherwise just add the object
                _Objects.Add(newObject);
            }
        }

        private void DistributeObjects()
        {
            for (int i = 0; i < _Objects.Count; i++)
            {
                for (int j = 0; j < _Nodes.Count; j++)
                {
                    if (_Nodes[j].Contains(_Objects[i].Position))
                    {
                        _Nodes[j].AddObject(_Objects[i]);
                    }
                }
            }

            _Objects.Clear();
        }

        private void Split()
        {
            float offset = _Length / 4.0f;

            for (int x = -1; x <= 1; x += 2)
            {
                for (int y = -1; y <= 1; y += 2)
                {
                    for (int z = -1; z <= 1; z += 2)
                    {
                        Scenenode newNode = new Scenenode(
                            _Center + new Vector3(x * offset, y * offset, z * offset),
                            offset * 2.0f
                        );

                        _Nodes.Add(newNode);
                    }
                }
            }

            DistributeObjects();
        }

        private bool Contains(Vector3 checkPoint)
        {
            return (_Bounds.Contains(checkPoint) == ContainmentType.Contains);
        }

        private void LoadGraphicsContent()
        {
            float halfLength = _Length / 2.0f;
            points = new VertexPositionColor[8];

            points[0] = new VertexPositionColor(_Center + new Vector3(-halfLength, -halfLength, -halfLength), Color.Red);
            points[1] = new VertexPositionColor(_Center + new Vector3(halfLength, -halfLength, -halfLength), Color.Red);
            points[2] = new VertexPositionColor(_Center + new Vector3(-halfLength, halfLength, -halfLength), Color.Red);
            points[3] = new VertexPositionColor(_Center + new Vector3(halfLength, halfLength, -halfLength), Color.Red);
            points[4] = new VertexPositionColor(_Center + new Vector3(-halfLength, -halfLength, halfLength), Color.Red);
            points[5] = new VertexPositionColor(_Center + new Vector3(halfLength, -halfLength, halfLength), Color.Red);
            points[6] = new VertexPositionColor(_Center + new Vector3(-halfLength, halfLength, halfLength), Color.Red);
            points[7] = new VertexPositionColor(_Center + new Vector3(halfLength, halfLength, halfLength), Color.Red);

            int[] inds = {
				0, 1, 0, 2, 1, 3, 2, 3,
				4, 5, 4, 6, 5, 7, 6, 7,
				0, 4, 1, 5, 2, 6, 3, 7
			};

            index = inds;

            Vector3 cornerDistance = new Vector3(halfLength);
            _Bounds = new BoundingBox(_Center - cornerDistance, _Center + cornerDistance);
        }

        public void Update(GameTime gameTime)
        {
            Culled = CameraManager.Instance.ActiveCamera.Frustum.Contains(_Bounds) == ContainmentType.Disjoint;

            if (!Culled)
            {
                for (int i = 0; i < _Objects.Count; i++)
                    _Objects[i].Update(gameTime);

                for (int j = 0; j < _Nodes.Count; j++)
                    _Nodes[j].Update(gameTime);
            }
        }

        public void Draw(GameTime gameTime)
        {
            if (!Culled)
            {
                if (Scene.DrawOctnodes) DrawNodeLines();

                // Draw the child objects
                for (int i = 0; i < _Objects.Count; i++)
                {
                    if (!((Scene)SceneManager.Instance.CurrentScene).ObjectsDrawn.Contains(_Objects[i].GetHashCode()))
                    {
                        _Objects[i].Draw(gameTime);
                        ((Scene)SceneManager.Instance.CurrentScene).ObjectsDrawn.Add(_Objects[i].GetHashCode());
                    }
                }

                // Draw the children down the tree
                for (int j = 0; j < _Nodes.Count; j++)
                {
                    _Nodes[j].Draw(gameTime);
                }
            }
        }

        private void DrawNodeLines()
        {
            if (_Nodes.Count == 0)
            {
                GameEngine.Instance.Device.VertexDeclaration = Scene.VertexDeclaration;

                BasicEffect b = ShaderManager.Instance.GetShader("BasicEffect").Effect as BasicEffect;
                b.World = CameraManager.Instance.ActiveCamera.World;
                b.View = CameraManager.Instance.ActiveCamera.View;
                b.Projection = CameraManager.Instance.ActiveCamera.Projection;
                b.DiffuseColor = Scene.LineColor.ToVector3();

                b.Begin();

                for (int i = 0; i < b.CurrentTechnique.Passes.Count; i++)
                {
                    b.CurrentTechnique.Passes[i].Begin();
                    GameEngine.Instance.Device.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList, points, 0, 8, index, 0, 12);
                    b.CurrentTechnique.Passes[i].End();
                }
                b.End();
            }
        }

        public void Clear()
        {
            _Nodes.Clear();
        }

        internal void LoadContent()
        {
            for (int i = 0; i < _Objects.Count; i++)
                _Objects[i].LoadContent();

            for (int j = 0; j < _Nodes.Count; j++)
                _Nodes[j].LoadContent(); 
        }
    }

    public class ScenenodeCollection : Collection<Scenenode> { }
    public class ObjectCollection : Collection<BaseObject> { }
}
